﻿using System;
using System.Collections.Generic;
using System.Text;
using SAPStudio.AvsFilterNet;
using System.IO;

// The following attribute is used by the loader to find your plugin class.
// You can specify more than one instance of this attribute to have multiple plugins in one assembly.
// Attribute signature: AvisynthFilterClass(Type type, String filterName, String arguments)
// Parameters:
//          type: Your plugin type. It must be derived from AvisynthFilter.
//    filterName: Plugin name. This is the name used in AVS scripts.
//     arguments: Plugin arguments. It is a string that defines the types and optional names of the
//                                  arguments for you filter.
//                                         c - Video Clip
//                                         i - Integer number
//                                         f - Float number
//                                         s - String
//                                         b - boolean

//                                 The word inside the [ ] lets you used named parameters in your script
//                                 e.g last=SimpleSample(last,windowclip,size=100).
//                                 but last=SimpleSample(last,windowclip, 100) will also work 

// Any type can be followed with a '*' or '+' to indicate "zero or more" or "one or more" respectively. 
// In this case all the matching arguments will be gathered into a sub-array.
// For example, if your type string is "is+f", then the integer argument will be args[0], 
// the string arguments will be args[1][0], args[1][1], etc. , and the float argument will be args[2].

// '.' matches a single argument of any type. To match multiple arguments of any type, use ".*" or ".+". 
[assembly: AvisynthFilterClass(typeof(SAPStudio.TimeCodeOutPut.TimeCodeOutPut), "TimeCodeOutPut", "cc+[TCFILE]s[INPUT]s")]
namespace SAPStudio.TimeCodeOutPut
{
    public class TimeCodeOutPut : AvisynthFilter
    {
        String tcfilepath;
        String inTcFilePath;
        //int tcVer;
        Clip[] videos;
        int[] videosBound;
        //VideoInfo vi;
        //StreamWriter logSW;

        public TimeCodeOutPut(AVSValue args, ScriptEnvironment env)
            : base(args, env)
        {
            // This is the constructor. It does not return any value, and is always used, 
            //  when an instance of the class is created.

            //The signature of constructor must be (AVSValue args, ScriptEnvironment env),
            //otherwise the loader won't recognize your plugin.

            //Save the parameter for future use.
            //Remember index 0 is the child clip saved in base class.
            //this.logSW = new StreamWriter(new FileStream("filterlog.txt", FileMode.Create, FileAccess.Write));
            //this.logSW.WriteLine("clip0: " + args[0].AsClip().GetVideoInfo().num_frames);
            //this.logSW.WriteLine("clip count to combin:" + args[1].ArraySize());
            //this.logSW.Flush();
            //this.logSW.Close();
            tcfilepath = args[2].AsString("TimeCode.txt");
            inTcFilePath = args[3].AsString("");
            //tcVer = args[4].AsInt(1);

            int videosCount = args[1].ArraySize() + 1;
            videos = new Clip[videosCount];
            videosBound = new int[videosCount];

            videos[0] = args[0].AsClip();
            for (int i = 1; i < videosCount; i++)
            {
                videos[i] = args[1][i - 1].AsClip();
            }

            TCFileOutPut();
        }

        protected override void Dispose(bool disposing)
        {
            // This is where you can deallocate any memory you might have used.
            if (disposing)
            {
                //Release any managed resources here.
                //NOTE: You must dispose all Clips(except for the Child clip since it is disposed in base class) 
                //when they are no longer needed, otherwise avisynth may crash at exit.
                foreach (Clip c in videos)
                {
                    c.Dispose();
                }

            }

            //Release any unmanaged resources here.

            //Don't forget to call this
            base.Dispose(disposing);
        }

        public void TCFileOutPut()
        {
            VideoInfo ori_vi = this.GetVideoInfo();
            //head of timecode v1 file, Assume FPS as the original clip
            String tcLinesV1 = "# timecode format v1" + Environment.NewLine + "Assume " + (ori_vi.fps_numerator * 1.0 / ori_vi.fps_denominator) + Environment.NewLine;
            int offset = 0;
            int firstVideoToDeal = 0;

            //若指定了input参数，表示开启append模式
            if (this.inTcFilePath.Length != 0)
            {
                TCFileTools oldTC = new TCFileTools(inTcFilePath);
                if (oldTC.checkTC() == false)
                {
                    //!error: irregular tc file
                    return;
                }
                oldTC.closeTCFile();
                //if (oldTC.frameCount != ori_vi.num_frames)
                //{
                //    //!error: the framecount of clip0 doesn't equals with the framecount of oldTC
                //    return;
                //}
                String firstSectionTcLines = "";
                foreach (String li in oldTC.content)
                {
                    firstSectionTcLines += li + Environment.NewLine;
                }
                tcLinesV1 = firstSectionTcLines;
                offset = ori_vi.num_frames - 1;
                firstVideoToDeal = 1;
            }

            for (int i = firstVideoToDeal; i < videos.Length; i++)
            {
                //get framecount and framerate, generate timecode line for this clip
                VideoInfo vi = videos[i].GetVideoInfo();
                int framecount = vi.num_frames;
                double framerate = vi.fps_numerator * 1.0 / vi.fps_denominator;
                String tcline = "" + offset + "," + (offset + framecount - 1) + "," + framerate + Environment.NewLine;
                tcLinesV1 += tcline;
                //plus offset for the next clip
                offset += framecount;
                //remember the last position of each clip
                this.videosBound[i] = offset;
            }
            //update to new framecount
            ori_vi.num_frames = this.videosBound[this.videosBound.Length - 1];
            this.SetVideoInfo(ref ori_vi);

            //write timecode to file
            try
            {
                FileStream tcFileFS = new FileStream(this.tcfilepath, FileMode.Create, FileAccess.Write);
                StreamWriter tcFileSW = new StreamWriter(tcFileFS);
                tcFileSW.Write(tcLinesV1);
                tcFileSW.Close();
            }
            catch (IOException e)
            {
                //error message, to do
                return;
            }
            return;
        }

        public override VideoFrame GetFrame(int n, ScriptEnvironment env)
        {
            //find which clip is Frame n in.
            int videonum = this.getClipNumbyN(n);
            int relativeFrameNum = 0;
            if (videonum == -1)
            {
                env.ThrowError("TimeCodeOutPut: Error to index Frame n");
            }
            //calculate relative position
            if (videonum == 0)
            {
                relativeFrameNum = n;
            }
            else
            {
                relativeFrameNum = n - this.videosBound[videonum - 1];
            }
            //get the real videoframe
            VideoFrame f = null;
            f = this.videos[videonum].GetFrame(relativeFrameNum, env);
            return f;
        }

        private int getClipNumbyN(int n)
        {
            if (n < 0)
            {
                return -1;
            }
            for (int i = 0; i < videosBound.Length; i++)
            {
                if (n < this.videosBound[i])
                {
                    return i;
                }
            }
            return -1;
        }
    }
}
